package com.gopay.checkorder.service.sentinel.impl;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import com.gopay.checkorder.dao.BankSentinelInfoDAO;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gopay.checkorder.service.sentinel.BcdlOrderSentinel;
import com.gopay.common.constants.bank.BankOrderStatus;
import com.gopay.common.constants.bank.BankOrderType;
import com.gopay.common.constants.bank.BisBankBusinessProd;
import com.gopay.common.constants.proccode.ProcCodeConstants;
import com.gopay.common.domain.bank.BankOrder;
import com.gopay.common.domain.bank.BcdlResult;
import com.gopay.common.domain.bank.bcdl.BcdlPacketRes;
import com.gopay.common.domain.cps.vo.CheckOrderResult;
import com.gopay.common.order.dao.CommonQueryDAO;
import com.gopay.common.util.DateUtils;
import com.gopay.common.util.StringTools;
import com.gopay.sms.client.GopaySMSHttpClient;
import com.gopay.sms.common.constants.SMSAppId;
import com.gopay.sms.common.domain.RequestVO;
import com.gopay.sms.common.domain.ResponseVO;

import org.springframework.stereotype.Component;

/**
 * 熔断机制
 * 
 * @author lizheng
 *
 */
public class BcdlOrderSentinelImpl implements BcdlOrderSentinel {
	private final static Logger logger = LoggerFactory.getLogger(BcdlOrderSentinelImpl.class);
	@Resource(name = "commonQueryDAO")
	private CommonQueryDAO commonQueryDAO;

	@Resource(name = "bankSentinelInfoDAO")
	private BankSentinelInfoDAO bankSentinelInfoDAO;
	
	private static final String SWITCH_ON = "on";

    @Resource(name = "smsConfig")
    private Map<String, String> smsConfig;

	/**
	 * 熔断机制true-中断 fales-通路
	 */
	private Boolean fuseWire = false;

	private int timeoutCount = 0;
	
	private int batchTimeoutCount = 0;

	private String bank;

	private Integer finishTime;

	private String sentinelId;
	
	/**
	 * 上次手动重启时间（非杀掉线程重启）
	 */
	private Date restartLastTime = null;
	
	@Resource(name = "bcdlConfig")
	private Map<String, String> bcdlConfig;

	/**
	 * 项目启动时初始化fuseWire
	 */
	public void initFuseWire(){
		fuseWire = bankSentinelInfoDAO.findByStlmEx(sentinelId);
	}

	@Override
	public Boolean isFuse() {
		logger.error("[sentinel_patrol_isFuse] bank=" + bank + ", finishTime=" + finishTime);
		return fuseWire;
	}
	
	@Override
	public Boolean restart() {
		timeoutCount = 0;
		batchTimeoutCount = 0;
		restartLastTime = new Date();
		fuseWire = false;
		bankSentinelInfoDAO.updateSentinel(0, 0, sentinelId, "", "");
		return true;
	}
	
	@Override
	public Boolean stop(int flag) {//0手工熔断 1熔断器逻辑熔断 2任务未完成熔断
		fuseWire = true;
		bankSentinelInfoDAO.updateSentinel(flag, 1, sentinelId, "", "");
		return true;
	}
	
	@Override
	public void batchPatrol(String packetNo, BcdlResult result, BcdlPacketRes packetRes) {
		// TODO Auto-generated method stub
		logger.error("[sentinel_batch_patrol_start]" + bank + ", PacketNo = " + packetNo);
		try {
			if (null == result) {
				fuseWiring( "未知结果中断付款作业", "PacketNo = "+packetNo);
				return;
			}
			
			if (ProcCodeConstants.PROC_CODE_100E6082.equals(result.getResultCode())) {
				// 银企直连类交易转账签名异常,中断交易发起
				fuseWiring( "签名异常中断付款作业", "PacketNo = "+packetNo);
				return;
			}
			
			if (ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())) {
				batchTimeoutCount++;
				if (batchTimeoutCount == 2) {
					fuseWiring( "连续2笔超时中断付款作业", "PacketNo = "+packetNo);
					return;
				}
			} else {
				batchTimeoutCount = 0;
			}
			
			if(!ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())
					&&!ProcCodeConstants.PROC_CODE_100S1000.equals(result.getResultCode())){
				fuseWiring( "批量订单付款未成功中断付款作业", "PacketNo = "+packetNo);
				return;
			}
			
			int batchCheckOrderSuccessRateThreshold = Integer.parseInt(bcdlConfig.get("batch.checkorder.successrate.threshold"));
			if(packetRes.isSuccess()&&packetRes.getBcdlResults()!=null&&packetRes.getBcdlResults().size()>batchCheckOrderSuccessRateThreshold){
				int successNum = 0;
				for(BcdlResult res:packetRes.getBcdlResults()){
					if(ProcCodeConstants.PROC_CODE_100S1000.equals(res.getResultCode())
							|| (ProcCodeConstants.PROC_CODE_100E6027.equals(res.getResultCode())&&res.isIgnoreResult())){
						successNum++;
					}
					if(ProcCodeConstants.PROC_CODE_100E6066.equals(res.getResultCode()) && res.isIgnoreResult()){
						successNum++;
					}
				}
				if(successNum==0){
					// 批量查询没有任意一笔订单成功
					fuseWiring( "批量查询，没有任意一笔订单成功", "PacketNo = "+packetNo);
					return;
				}
//		        double accuracy_num = successNum * 100 / packetRes.getBcdlResults().size();
//		        if(accuracy_num<50){
//		        	// 批量查询尝试多次查询，成功率未达到50%
//					fuseWiring( "多次查询批量查询，成功率未达到50%", "PacketNo = "+packetNo);
//					return;
//		        }
			}
		} catch (Exception e) {
			logger.error("[sentinel_patrol_error]" + bank + ",哨兵异常"+ "," + "PacketNo = "+packetNo, e);
			fuseWiring( "哨兵异常中断付款作业", "PacketNo = "+packetNo);
			return;
		}
		logger.error("[sentinel_patrol_end]" + bank + "," + "PacketNo = "+packetNo);
	
	}

	@Override
	public void stopForTime(Long sq) {
		fuseWire = true;
		bankSentinelInfoDAO.updateSentinelAndTimeFuse(1, sentinelId, sq);
	}

    @Override
    public void restartForTime(Long sq) {
        timeoutCount = 0;
        batchTimeoutCount = 0;
        restartLastTime = new Date();
        fuseWire = false;
        bankSentinelInfoDAO.updateSentinelAndTimeFuse(0, sentinelId,sq);
    }

    @Override
	public void patrol(BankOrder bankOrder, BcdlResult result) {
		logger.error("[sentinel_patrol_start]" + bank + "," + bankOrder.getOrderId());
		try {
			if (null == result) {
				fuseWiring( "未知结果中断付款作业", bankOrder.getOrderId());
				return;
			}
			
			if (ProcCodeConstants.PROC_CODE_100E6082.equals(result.getResultCode())) {
				// 银企直连类交易转账签名异常,中断交易发起
				fuseWiring( "签名异常中断付款作业", bankOrder.getOrderId());
				return;
			}
			
			if (ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())) {
				timeoutCount++;
				if (timeoutCount == 5) {
					fuseWiring( "连续5笔超时中断付款作业", bankOrder.getOrderId());
					return;
				}
			} else {
				timeoutCount = 0;
			}
			
			if (checkFail()) {
				fuseWiring( "连续5笔失败中断付款作业", bankOrder.getOrderId());
				return;
			}
		} catch (Exception e) {
			logger.error("[sentinel_patrol_error]" + bank + ",哨兵异常"+ "," + bankOrder.getOrderId(), e);
			fuseWiring( "哨兵异常中断付款作业", bankOrder.getOrderId());
			return;
		}
		logger.error("[sentinel_patrol_end]" + bank + "," + bankOrder.getOrderId());
	}

	public void fuseWiring(String msg,String gopayOrderId) {
		fuseWire = true;
		bankSentinelInfoDAO.updateSentinel(1, 1, sentinelId, msg, gopayOrderId);
		logger.error("[fuseWiring]" + bank + ","+msg + ","+gopayOrderId);
		sendSms(msg,gopayOrderId);
	}
	
	public Boolean checkFail() {
		List<Object[]> list = getBcdlOrderByFinishTime(bank, 1, finishTime);
		int failCount = 0;
		for (Object[] obj : list) {
			logger.error("[sentinel_check_fail]" + bank + "," + StringTools.checkNulltoString(obj[0])+","+StringTools.checkNulltoString(obj[1]));
			if (StringUtils.equals(StringTools.checkNulltoString(obj[1]), BankOrderStatus.FAIL)) {
				failCount++;
			}
		}
		return failCount == finishTime;
	}

	public List<Object[]> getBcdlOrderByFinishTime(String bank, int pageIndex, int pageSize) {
		StringBuffer sql = new StringBuffer(250);
		sql.append("select b.GOPAY_ORDER_ID,b.ORDER_STATUS from BIS_BANK_MAIN_ORDER b ");
		sql.append(" where b.ORDER_STATUS != :orderStatus ");
		sql.append(" and b.BANK_BIZ_PROD_CODE = :bankBizProdCode ");
		sql.append(" and b.ORDER_TYPE = :orderType ");
		sql.append(" and b.GOPAY_BANK_CODE = :bankCode ");
		sql.append(" and b.tran_finish_time is not null ");
		sql.append(" and b.SEND_BANK_TM >to_date(:beginTime ,'yyyyMMddHH24miss') ");
		sql.append(" order by b.SEND_BANK_TM desc ");

		Map<String, Object> params = new HashMap<String, Object>();
		params.put("orderStatus", BankOrderStatus.ACCEPTED);
		params.put("bankBizProdCode", BisBankBusinessProd._09.value);
		params.put("orderType", BankOrderType.GOPAY_BIS_ORDER);
		params.put("bankCode", bank);
		params.put("beginTime", recentlyTime());
		List<Object[]> list = commonQueryDAO.findBySql(sql.toString(), params, pageIndex, pageSize);
		return list;
	}

	private String recentlyTime(){
		Calendar calendar = Calendar.getInstance();
		
		//开始时间--最近1天
		calendar.setTime(new Date());
		calendar.add(Calendar.DAY_OF_MONTH, -1);
		Date begin = calendar.getTime();
		
		if(restartLastTime!=null&&restartLastTime.after(begin)){
			begin = restartLastTime;
		}
		String beginTime=DateUtils.format(begin, "yyyyMMddHHmmss");
		
		return beginTime;
	}


    private void sendSms(String msg,String gopayOrderId) {

       try {
           if (SWITCH_ON.equals(smsConfig.get("switch"))) {
                // 自定义短信内容，不提供验证码校验功能
                RequestVO req = new RequestVO();
                req.setAppId(SMSAppId._90000.value);
                req.setMobile(smsConfig.get("mobile"));// 电话号码(多个以逗号分隔)
                if(gopayOrderId.startsWith("PacketNo")){
                	req.setContent(DateUtils.getCurrDate() + "," +bank + " 熔断:" + msg + ", " + gopayOrderId);
                }else{
                	req.setContent(DateUtils.getCurrDate() + "," +bank + " 熔断:" + msg + ", gopayOrderId:" + gopayOrderId);
                }
                ResponseVO resp = GopaySMSHttpClient.getInstance().createVerificationCode(req);

                if (resp != null) {
                    if ("100".equals(resp.getStatusCode())) {
                        logger.error("[sentinel_sms]银行【" + bank + "】熔断短信发送成功");
                    } else {
                        logger.error("[sentinel_sms]银行【" + bank + "】熔断短信发送失败，原因：" + resp.getMessage());
                    }
                }
            } else {
                logger.error("[sentinel_sms] 熔断短信发送已关闭，信息: 银行" + bank);

            }
        } catch (Exception e) {
            logger.error("[sentinel_sms]senSMS error: ", e);
        }

    }
    
	public void setBank(String bank) {
		this.bank = bank;
	}
	
	@Override
	public String getBank() {
		return bank;
	}

	public void setFinishTime(Integer finishTime) {
		this.finishTime = finishTime;
	}

	@Override
	public void checkPartol(BankOrder bankOrder, CheckOrderResult result) {
		// TODO Auto-generated method stub
		
	}

	public String getSentinelId() {
		return sentinelId;
	}

	public void setSentinelId(String sentinelId) {
		this.sentinelId = sentinelId;
	}

	@Override
	public void batchCheckPatrol(String packetNo, BcdlPacketRes res) {
		// TODO Auto-generated method stub
		
	}
}
